package com.panda.admin.login.authconfig;

import com.panda.admin.login.auth.service.impl.SysUserDetailServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

import java.security.KeyPair;
import java.util.Arrays;

/**
 * author: tcy
 * createDate: 2022/10/26
 * description:认证授权服务配置
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private ClientDetailsService clientDetailsService;

    @Autowired
    private SysUserDetailServiceImpl sysUserDetailService;

    /**
     * redis工厂
     */
    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    /**
     * 用来配置令牌的安全约束
     * 拦截控制
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) {
        security.checkTokenAccess("permitAll()")                                 // check_token公开
                .tokenKeyAccess("permitAll()")                                   // token_key公开
                .allowFormAuthenticationForClients();                            // 表单认证（申请令牌）
    }

    /**
     * 用来配置客户端详情服务（ClientDetailsService）
     * 允许客户端自己申请ClientID
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        /**
         * 数据库中添加客户端信息表，可以启用withClientDetails
         * 还需要自己建立ClientDetailsService的实现类ClientDetailsServiceImpl
         * ClientDetailsServiceImpl implements ClientDetailsService
         * 在实现类中将客户端相关数据配置好
         */
        //clients.withClientDetails(clientDetailsService);

        /**
         * 客户端配置唯一，直接在代码中配置也可以
         * 但是保密性不强，安全性不高
         */
        clients.inMemory()
               .withClient("client")                                               //注册客户端id
               .secret(passwordEncoder.encode("secret"))                      //注册客户端密码
               .accessTokenValiditySeconds(36000)                                         //有效时间10h
               .authorizedGrantTypes("authorization_code","password","refresh_token")     //三方登录、密码授权、刷新令牌
               .autoApprove(true)                                                         //登录后绕过批准询问(/oauth/confirm_access)
               .scopes("all")                                                             //允许授权范围
               .redirectUris("https://gitee.com/");                                       //回调网址，携带授权码
    }

    /**
     * 管理令牌：用来配置令牌（token）的访问端点和令牌服务(token services)
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {

        //token增强
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer(), jwtAccessTokenConverter()));

        endpoints.authenticationManager(authenticationManager)
                 .accessTokenConverter(jwtAccessTokenConverter())
                 .tokenStore(tokenStore())
                 .tokenEnhancer(tokenEnhancerChain)

                /** refresh token有两种使用方式：重复使用(true)、非重复使用(false)，默认为true
                 *  1 重复使用：access token过期刷新时， refresh token过期时间未改变，仍以初次生成的时间为准
                 *  2 非重复使用：access token过期刷新时， refresh token过期时间延续，在refresh token有效期内刷新便永不失效达到无需再次登录的目的
                 */
                .reuseRefreshTokens(true)
                .userDetailsService(sysUserDetailService);
    }

    /**
     * 自定义的Token增强器，把更多信息放入Token中
     * 登录者的id、角色、手机号、其他绑定账号等
     */
    @Bean
    public TokenEnhancer tokenEnhancer() {
        return new EnhanceTokenEnhancer();
    }

    /**
     * 配置JWT令牌
     */
    @Bean
    protected JwtAccessTokenConverter jwtAccessTokenConverter() {
        //设置jwt的转换器
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();

        /**
         * 非对称加密：密钥库中获取密钥对(公钥+私钥)
         * 需要 *.jks文件
         */
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jks/pandas.jks"), "pandas".toCharArray());
        KeyPair keyPair = keyStoreKeyFactory.getKeyPair("pandas","pandas".toCharArray());             //设置获取秘钥的密码
        converter.setKeyPair(keyPair);                                                                     //设置秘钥对象

        /**
         * 对称加密
         */
        //converter.setSigningKey("Tu");
        return converter;
    }

    /**
     * JWT令牌存储
     */
    @Bean
    public TokenStore tokenStore() {

        /**
         * springSecurity中TokenStore实现有4种，分别是：
         *    InMemoryTokenStore（保存本地内存）
         *    JdbcTokenStore（保存到数据库）
         *    JwkTokenStore（全部信息返回到客户端）
         *    RedisTokenStore（保存到redis）
         */

        //存放至redis库，注意pom和yml中相关配置
        RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory);
        redisTokenStore.setPrefix("authToken:");
        return redisTokenStore;

        //return new JwtTokenStore(jwtAccessTokenConverter());
    }


}
